import java.util.Hashtable;
import java.util.ArrayList;

class Functor {
    String name; int arity;
    
    public Functor(String name, int arity) {
        this.name = name;
        this.arity = arity;
    }
    
    public int hashCode() {
        return name.hashCode() ^ arity;
    }
    
    public boolean equals(Object o) {
        Functor f = (Functor)o;
        return arity == f.arity && name.equals(f.name);
    }
}

class Clause {
    Compound head;
    Compound body;
    
    public Clause(Compound head, Compound body) {
        this.head = head;
        this.body = body;
    }
}

public class DynamicSink extends Sink {
    Hashtable<Functor,ArrayList<Clause>> predicates;
    
    public DynamicSink(Sink next) {
        this.next = next;
        
        predicates = new Hashtable<Functor,ArrayList<Clause>>();
    }
    
    public Stack<Choicepoint> fishFor
        (String name, int arity, Compound term, int choice,
         Prolog prolog, Unifier unifier, int cutIndex,
         Stack<Conjunct> continuation, Stack<Choicepoint> choicepoints) {
        Choicepoint choicepoint;
        int height = choicepoints.height;
        
        if(name.equals("assert") && arity == 1) {
            if(choice == 0) {
                Compound thing = (Compound)term.args[0].dereference();
                // TODO: do term expansion?
                Clause toAssert;
                if(thing.name.equals(":-") && thing.arity == 2) {
                    toAssert = new Clause((Compound)thing.args[0].dereference(),
                                          (Compound)thing.args[1].dereference());
                }
                else {
                    toAssert = new Clause(thing, null);
                }
                Functor f = new Functor(toAssert.head.name, toAssert.head.arity);
                ArrayList<Clause> these = predicates.get(f);
                if(these == null) {
                    these = new ArrayList<Clause>();
                    these.add(toAssert);
                    predicates.put(f,these);
                }
                else {
                    these.add(toAssert);
                }
                return new Stack<Choicepoint>(new Choicepoint(continuation,height),choicepoints);
            }
            else {
                choicepoints.head.dead = true;
                return choicepoints;
            }
        }
        else if(name.equals("retract") && arity == 1) {
            choicepoints.head.dead = true;
            return choicepoints;
        }
        else {
            ArrayList<Clause> these = predicates.get(new Functor(name,arity));
            if(these != null) {
                if(choice < these.size()) {
                    Clause todo = these.get(choice);
                    if(unifier.unify(term,todo.head)) {
                        if(todo.body == null) {
                            choicepoint = new Choicepoint(continuation,height);
                        }
                        else {
                            choicepoint = new Choicepoint(continuation,height, todo.body);
                        }
                        choicepoint.bindings = unifier.bound;
                        return new Stack<Choicepoint>(choicepoint,choicepoints);
                    }
                    else {
                        choicepoints.head.dead = true;
                        return choicepoints;
                    }
                }
                else {
                    choicepoints.head.dead = true;
                    return choicepoints;
                }
            }
        }
        
        return next.fishFor
            (name,arity,term,choice,
             prolog,unifier,cutIndex,
             continuation,choicepoints);
    }
}
